/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*				PROJETO III - PROGRAMACAO CONCORRENTE
*							 SSC0143				  PROF.: JULIO ESTRELLA
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*	DESCRICAO:	SMOOTHING DE IMAGENS OPEN MPI E CUDA
*
*	LINGUAGEM:	C
*				
*	GRUPO - 8:	CARLOS HUMBERTO DOS SANTOS BAQUETA			  NUSP: 7987456
*	(TURMA-A)	RICARDO    ISSAMU   FUKUDA   GUNZI			  NUSP: 7986729
*				
*	
*	ENTREGA  :	07/12/2014									  BCC-012
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "mpi.h"
#include <stdio.h>
#include <stdlib.h> 
#include <string.h> 
#include <stddef.h>
#include <math.h> 
// Library that allows to use time functions to evaluate the performance
#include <sys/time.h>

#define SMOOTH_LEVEL 5
// #define BUFFER_SIZE ((width+((int)SMOOTH_LEVEL/2)*2)*(height+((int)SMOOTH_LEVEL/2)*2))/num_processes // usado pela Thread ROOT
#define BUFFER_SIZE (num_rows+(2*((int)SMOOTH_LEVEL/2)))*width2; // usado pela Thread ROOT
#define NUM_PARAMS 3 // Os quais podem ser:
	// 1 - num_rows
	#define NUMBER_ROWS 0
	// 2 - width
	#define WIDTH 1
	// 3 - sizeOfBuffer
	#define SIZE_BUFFER 2

#define ROOT 0



int max(int a, int b){
	if (a>b){return a;}
	return b;
}



main(int argc, char **argv )
{
	int rank, num_processes, type = 99;

	MPI_Init(&argc, &argv);
	MPI_Comm_size(MPI_COMM_WORLD, &num_processes);
	MPI_Comm_rank(MPI_COMM_WORLD, &rank);
	
	FILE *fp=NULL;// Para posterior calculo de speedUp

	MPI_Status status;
	int num_rows;
	int i, j, k, width, height, width2, height2, maxValue, value, soma, rc;

	int *sendcounts;    // array describing how many elements to send to each process
    int *displs;        // array describing the displacements where each segment begins

    int params[NUM_PARAMS];

  	unsigned char *rgb[3]={NULL, NULL, NULL}, *out[3]={NULL, NULL, NULL};
	char format[3];

	unsigned char *RED, *GREEN, *BLUE;
	unsigned char *smooth_buffer;// Guardará a parte da imagem, após o smoothing, dentro de cada processo
	unsigned char *recv_data[3]; // Buffer utilizado na etapa de junção (gather) das computações realizadas em cada processo
	unsigned char *buffer;	 	 // Buffer no qual residirão os pixels da faixa direcionada a cada processo
	int sizeOfBuffer;		 	 // Tamanho de cada faixa da imagem que será atribuída aos processos
	smooth_buffer = NULL;
	buffer = NULL;

	double t_start, t_end, t_mpi;



	if(rank == ROOT){ // Processo ROOT
		/// leitura do cabeçalho do arquivo
		scanf("%s", format);
		scanf("%d %d %d", &width, &height, &maxValue);
		
		/// erro: formato incorreto, nao é rgb.
		if(strcmp(format, "P3")){
			return -1;
		}
		
		/// erro: nao é unsigned char.
		if(maxValue > 255){
			return -1;
		}
		
		width2 = width+(2*((int)SMOOTH_LEVEL/2));
		height2 = height+(2*((int)SMOOTH_LEVEL/2));

		// Aloca as matrizes rgb
		for(i=0; i < 3; i++){
			rgb[i] = (unsigned char*) calloc(height2*width2, sizeof(unsigned char));
			out[i] = (unsigned char*) calloc(height*width, sizeof(unsigned char)); //p/ seq
		}
		
		// Le os valores rgb de cada pixel da imagem e coloca na matriz correspondente
		for(i=0; i < height; i++){
			for(j=0; j < width; j++){
				for(k=0; k < 3; k++){
					scanf("%d",  &value);
					rgb[k][(i+2)*width2 + j + 2] = (unsigned char)value;
				}
			}
		}

		RED = (unsigned char*) calloc(height2*width2, sizeof(unsigned char));
		GREEN = (unsigned char*) calloc(height2*width2, sizeof(unsigned char));
		BLUE = (unsigned char*) calloc(height2*width2, sizeof(unsigned char));
		// Associando as matrizes de cor à rgb expandida
		for(i=0; i < height2; i++){
			for(j=0; j < width2; j++){
					RED[(i)*width2 + j] = rgb[0][(i)*width2 + j];
					GREEN[(i)*width2 + j] = rgb[1][(i)*width2 + j];
					BLUE[(i)*width2 + j] = rgb[2][(i)*width2 + j];
			}
		}

		// Número de linhas da imagem dentro da faixa
		num_rows = height/num_processes;

	    // Carrega os parâmetros para o vetor de parâmetros	    
	    params[NUMBER_ROWS] = num_rows;
	    params[WIDTH] = width;

	    // Aloca memória do tamanho da imagem original para o buffer receptor das partes da imagem, 
	    // após aplicação do smoothing por cada processo
	    for (i=0; i<3; i++)
	  		recv_data[i] = (unsigned char *) malloc(sizeof(unsigned char)*num_processes*num_rows*width);
	}

    // Envia os parâmetros necessários para a computação dentro dos demais processos 
    MPI_Bcast(&params, NUM_PARAMS, MPI_INT, ROOT, MPI_COMM_WORLD);
		
	// TEMPO DE EXECUCAO
	// Sincronização antes de iniciar a contagem de tempo
    MPI_Barrier(MPI_COMM_WORLD);
    if(rank == ROOT) { t_start = MPI_Wtime(); }

	// Recebe os parâmetros necessários para relizar a computação:
	num_rows = params[NUMBER_ROWS];
	width = params[WIDTH];
	width2 = width + (2*((int)SMOOTH_LEVEL/2));
    
    // Cálculo do tamanho do buffer
    sizeOfBuffer = (num_rows+(2*((int)SMOOTH_LEVEL/2)))*width2; 

    // Aloca os vetores que serão utilizados na chamada a MPI_Scatterv
	sendcounts = (int *) malloc(sizeof(int)*num_processes); // conterá o tamanho de cada faixa de imagem
    displs = (int *) malloc(sizeof(int)*num_processes);		// conterá o offset do início  de cada faixa
    														// considerando os pixels  extra  da   borda

    // Calcula os valores dos vetores sendcounts e displs(displacements/offset)
    for (i = 0; i < num_processes; i++) {
        sendcounts[i] = sizeOfBuffer;
        if(i == 0) {
        	displs[i] = 0; // Na primeira faixa da imagem o offset é zero
        } else {
        	// Calcula o offset correspondente  a  i-ésima  parte  da  imagem,  com i>0, 
        	// considerando as faixas de pixels extras necessárias para aplicar o smooth 
        	// nos pixels  da  borda  da  faixa  de  imagem   dentro   de  um   processo
        	displs[i] = ((i*num_rows)*width2); 
        }
    }

	// Aloca o buffer que será utilizado nos processos para guardar sua faixa de imagem
	buffer = (unsigned char*) calloc(sizeOfBuffer, sizeof(unsigned char));

	// Aloca o buffer que será utilizado nos processos para o cálculo do smoothing
	smooth_buffer = (unsigned char*) calloc(num_rows*width, sizeof(unsigned char));

	/**
	 * Canal Vermelho
	 * Envia partes contíguas da imagem, relativa à cor vermelha, aos processos correspondentes, 
	 * inclusive para o processo ROOT, que receberá a primeira parte da imagem
	**/
	rc = MPI_Scatterv(RED, sendcounts, displs, MPI_UNSIGNED_CHAR, buffer, sizeOfBuffer, MPI_UNSIGNED_CHAR, ROOT, MPI_COMM_WORLD); 
	if(rc == MPI_SUCCESS){
		// printf("MPI_SUCCESS\n");
	}else { // Erro
		if(rc == MPI_ERR_COMM){
			printf("MPI_ERR_COMM\n");
		}
		if(rc == MPI_ERR_COUNT){
			printf("MPI_ERR_COUNT\n");
		}
		if(rc == MPI_ERR_TYPE){
			printf("MPI_ERR_TYPE\n");
		}
		if(rc == MPI_ERR_BUFFER){
			printf("MPI_ERR_BUFFER\n");
		}
	}

	// Aplicação do smoothing na imagem
	unsigned int n=0, m=0;
	for(i=0; i < num_rows; i++){
		unsigned int inicY = (unsigned int)i-2;
		unsigned int limitY = (unsigned int)i+3;
		
		for(j=0; j < width; j++){
			unsigned int inicX = (unsigned int)j-2;
			unsigned int limitX = (unsigned int)j+3;
									
			soma=0;	
			
			for(n=inicY;n<limitY;n++){
				for(m=inicX; m<limitX; m++){
					soma += buffer[(n+2)*width2+m+2];
				}
			}
			smooth_buffer[i*width + j] = (unsigned char)((unsigned int)soma/25); // 0 -> RED
		}
	}

	/**
	 * Após o processo de smoothing, aplicado em cada faixa da imagem, a chamada a MPI_Gather resultará na união de todas as 
	 * faixas da imagem, associada a um determinado canal, no processo ROOT.
	 *
	**/ 
	MPI_Gather(smooth_buffer, sizeof(unsigned char)*num_rows*width, MPI_UNSIGNED_CHAR, recv_data[0], (sizeof(unsigned char)*num_rows*width), MPI_UNSIGNED_CHAR, ROOT, MPI_COMM_WORLD);

	/**
	 * Canal Verde
	 * Envia partes contíguas da imagem, relativa à cor verde, aos processos correspondentes, 
	 * inclusive para o processo ROOT, que receberá a primeira parte da imagem
	**/
	rc = MPI_Scatterv(GREEN, sendcounts, displs, MPI_UNSIGNED_CHAR, buffer, sizeOfBuffer, MPI_UNSIGNED_CHAR, ROOT, MPI_COMM_WORLD); 
	if(rc == MPI_SUCCESS){
		// printf("MPI_SUCCESS\n");
	}else { // Erro
		if(rc == MPI_ERR_COMM){
			printf("MPI_ERR_COMM\n");
		}
		if(rc == MPI_ERR_COUNT){
			printf("MPI_ERR_COUNT\n");
		}
		if(rc == MPI_ERR_TYPE){
			printf("MPI_ERR_TYPE\n");
		}
		if(rc == MPI_ERR_BUFFER){
			printf("MPI_ERR_BUFFER\n");
		}
	}

	// Aplicação do smoothing na imagem
	for(i=0; i < num_rows; i++){
		unsigned int inicY = (unsigned int)i-2;
		unsigned int limitY = (unsigned int)i+3;
		
		// printf("inicY: %d e limitY: %d\n", inicY, limitY);

		for(j=0; j < width; j++){
			unsigned int inicX = (unsigned int)j-2;
			unsigned int limitX = (unsigned int)j+3;
									
			soma=0;	
			
			for(n=inicY;n<limitY;n++){
				for(m=inicX; m<limitX; m++){
					soma += buffer[(n+2)*width2+m+2];
			// printf("soma: %d | indice: %d   | sizeOfBuffer: %d  |  inicX: %d  |  limitX: %d |  inicY: %d |  limitY: %d \n", soma, (i*width + j), (int)(num_rows+2)*width2, inicX, limitX, inicY,limitY );
				}
			}

			smooth_buffer[i*width + j] = (unsigned char)((unsigned int)soma/25); // 0 -> RED
			// printf("smooth_buffer[%d]: %d\n", i*width + j, smooth_buffer[i*width + j]);
		}
		// break;
	}

	/**
	 * Após o processo de smoothing, aplicado em cada faixa da imagem, a chamada a MPI_Gather resultará na união de todas as 
	 * faixas da imagem, associada a um determinado canal, no processo ROOT.
	 *
	**/ 
	MPI_Gather(smooth_buffer, sizeof(unsigned char)*num_rows*width, MPI_UNSIGNED_CHAR, recv_data[1], (sizeof(unsigned char)*num_rows*width), MPI_UNSIGNED_CHAR, ROOT, MPI_COMM_WORLD);

	/**
	 * Canal Blue
	 * Envia partes contíguas da imagem, relativa à cor azul, aos processos correspondentes, 
	 * inclusive para o processo ROOT, que receberá a primeira parte da imagem
	**/
	rc = MPI_Scatterv(BLUE, sendcounts, displs, MPI_UNSIGNED_CHAR, buffer, sizeOfBuffer, MPI_UNSIGNED_CHAR, ROOT, MPI_COMM_WORLD); 
	if(rc == MPI_SUCCESS){
		// printf("MPI_SUCCESS\n");
	}else { // Erro
		if(rc == MPI_ERR_COMM){
			printf("MPI_ERR_COMM\n");
		}
		if(rc == MPI_ERR_COUNT){
			printf("MPI_ERR_COUNT\n");
		}
		if(rc == MPI_ERR_TYPE){
			printf("MPI_ERR_TYPE\n");
		}
		if(rc == MPI_ERR_BUFFER){
			printf("MPI_ERR_BUFFER\n");
		}
	}
	
	// Aplicação do smoothing na imagem
	for(i=0; i < num_rows; i++){
		unsigned int inicY = (unsigned int)i-2;
		unsigned int limitY = (unsigned int)i+3;
		
		for(j=0; j < width; j++){
			unsigned int inicX = (unsigned int)j-2;
			unsigned int limitX = (unsigned int)j+3;
									
			soma=0;	
			
			for(n=inicY;n<limitY;n++){
				for(m=inicX; m<limitX; m++){
					soma += buffer[(n+2)*width2+m+2];
				}
			}
			smooth_buffer[i*width + j] = (unsigned char)((unsigned int)soma/25); // 0 -> RED
		}
	}
	/**
	 * Após o processo de smoothing, aplicado em cada faixa da imagem, a chamada a MPI_Gather resultará na união de todas as 
	 * faixas da imagem, associada a um determinado canal, no processo ROOT.
	 *
	**/ 		
	MPI_Gather(smooth_buffer, sizeof(unsigned char)*num_rows*width, MPI_UNSIGNED_CHAR, recv_data[2], (sizeof(unsigned char)*num_rows*width), MPI_UNSIGNED_CHAR, ROOT, MPI_COMM_WORLD);

	// Synchronize before starting timing
    MPI_Barrier(MPI_COMM_WORLD);
	if(rank == ROOT) { 
		t_end = MPI_Wtime();
		t_mpi = t_end - t_start;
		char *arq_name;
		if (argc > 1) {
			arq_name = argv[1];
			if ((fp = fopen(arq_name, "a+")) == NULL){
	          //  fprintf(stderr, "Error opening outfile: out.ppm\n");
	        }else{
	        	fprintf(fp, "%d\t%.10f\n", num_processes, t_mpi);
	        	fclose(fp);
	        }
		}
	}

	// Geração da nova imagem, após a aplicação do smoothing na imagem de entrada
	if(rank == ROOT){
		// Exibe o cabeçalho da imagem
		printf("P3\n%d %d\n%d\n", width, height, maxValue);
	
		// Exibe as matrizes resultado na tela
		 for(i=0; i < height; i++){
			for(j=0; j < width; j++){
				for(k=0; k < 3; k++){
					printf("%d ", (unsigned char)recv_data[k][i*width + j]);
				}
			}
			printf("\n");
		 }
	}

	// Libera memória ocupada pelas estruturas do MPI
	MPI_Finalize();
	if(rank == ROOT){
	// Desaloca os buffers criados dentro do processo ROOT
		for(i=0; i < 3; i++){
			if(rgb[i] != NULL){
				free(rgb[i]);
				rgb[i] = NULL;
			}
		}
		if (sendcounts != NULL) { free(sendcounts); sendcounts = NULL; }
	    if (displs != NULL) { free(displs); displs = NULL; }
		if (RED !=NULL) { free(RED); RED = NULL; }
		if (GREEN !=NULL) { free(GREEN); GREEN = NULL; }
		if (BLUE !=NULL) { free(BLUE); BLUE = NULL; }
	}
	// Desaloca os buffers criados dentro de cada processo
	if (smooth_buffer !=NULL) { free(smooth_buffer); smooth_buffer = NULL; }
	if (buffer !=NULL) { free(buffer); buffer = NULL; }
	return 0;
}